﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Net;

namespace VNCServer
{
    class RfbProtocol
    {
        // Encoding Constants
        public const int RAW_ENCODING = 0;
        public const int COPYRECT_ENCODING = 1;
        public const int RRE_ENCODING = 2;
        public const int CORRE_ENCODING = 4;
        public const int HEXTILE_ENCODING = 5;
        public const int ZRLE_ENCODING = 16;

        // Server to Client Message-Type constants
        public const int FRAMEBUFFER_UPDATE = 0;
        public const int SET_COLOUR_MAP_ENTRIES = 1;
        public const int BELL = 2;
        public const int SERVER_CUT_TEXT = 3;

        // Client to Server Message-Type constants
        protected const byte SET_PIXEL_FORMAT = 0;
        protected const byte SET_ENCODINGS = 2;
        protected const byte FRAMEBUFFER_UPDATE_REQUEST = 3;
        protected const byte KEY_EVENT = 4;
        protected const byte POINTER_EVENT = 5;
        protected const byte CLIENT_CUT_TEXT = 6;

        protected int verMajor;	// Major version of Protocol--probably 3
        protected int verMinor; // Minor version of Protocol--probably 3, 7, or 8

        protected TcpClient clientTcp;		// Network object used to communicate with host
        protected NetworkStream clientStream;	// Stream object used to send/receive data
        protected BinaryReader clientReader;	// Integral rather than Byte values are typically
        protected BinaryWriter clientWriter;	// sent and received, so these handle this.

        protected TcpListener listener;
        protected TcpClient serverTcp;		// Network object used to communicate with host
        protected NetworkStream serverStream;	// Stream object used to send/receive data
        protected BinaryReader serverReader;	// Integral rather than Byte values are typically
        protected BinaryWriter serverWriter;	// sent and received, so these handle this.

        public RfbProtocol()
        {

        }

        public void ConnectServer()
        {
            clientTcp = new TcpClient();
            clientTcp.Connect("ivy-pc", 5900);

            clientStream = clientTcp.GetStream();
            clientReader = new BigEndianBinaryReader(clientStream);
            clientWriter = new BigEndianBinaryWriter(clientStream);
        }

        public void StartListen()
        {
            System.Text.Encoding enc = System.Text.Encoding.UTF8;
            string host = System.Net.Dns.GetHostName();
            System.Net.IPAddress ipAdd = null;
            foreach (System.Net.IPAddress ipAddress in
                System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList)
            {
                if (!ipAddress.IsIPv6LinkLocal)
                    ipAdd = ipAddress;
            }
            if (null==ipAdd)
            {
                return;
            }
            listener = new TcpListener(IPAddress.Any, 5900);

            try
            {
                listener.Start();
            }
            catch (System.Exception ex)
            {
            	return;
            }

            while (true)
            {
                serverTcp = listener.AcceptTcpClient();
                if (serverTcp!=null)
                {
                    serverStream = serverTcp.GetStream();
                    serverReader = new BigEndianBinaryReader(serverStream);
                    serverWriter = new BigEndianBinaryWriter(serverStream);

                    //clientTcp.Connect("ivy-pc", 5900);

                    //do 
                    //{
                    //} while (serverStream.DataAvailable);

                    WriteProtocolVersion();
                    ReadProtocolVersion();
                    WriteSecurityTypes();

                    serverTcp.Close();
                    listener.Stop();
                    break;
                }
            }
        }

        public void WriteProtocolVersion()
        {
            byte[] b = new byte[12];
            b[0] = 0x52;
            b[1] = 0x46;
            b[2] = 0x42;
            b[3] = 0x20;
            b[4] = 0x30;
            b[5] = 0x30;
            b[6] = 0x33;
            b[7] = 0x2e;
            b[8] = 0x30;
            b[9] = 0x30;
            b[10] = 0x38;
            b[11] = 0x0a;

            serverWriter.Write(b);
            serverWriter.Flush();
        }

        public void ReadProtocolVersion()
        {
            byte[] b = serverReader.ReadBytes(12);

            // As of the time of writing, the only supported versions are 3.3, 3.7, and 3.8.
            if (b[0] == 0x52 &&					// R
                    b[1] == 0x46 &&					// F
                    b[2] == 0x42 &&					// B
                    b[3] == 0x20 &&					// (space)
                    b[4] == 0x30 &&					// 0
                    b[5] == 0x30 &&					// 0
                    b[6] == 0x33 &&					// 3
                    b[7] == 0x2e &&					// .
                   (b[8] == 0x30 ||                    // 0
                    b[8] == 0x38) &&					// BUG FIX: Apple reports 8 
                   (b[9] == 0x30 ||                     // 0
                    b[9] == 0x38) &&					// BUG FIX: Apple reports 8 
                   (b[10] == 0x33 ||					// 3, 7, OR 8 are all valid and possible
                    b[10] == 0x36 ||					// BUG FIX: UltraVNC reports protocol version 3.6!
                    b[10] == 0x37 ||
                    b[10] == 0x38 ||
                    b[10] == 0x39) &&                   // BUG FIX: Apple reports 9					
                    b[11] == 0x0a)						// \n
            {
                // Since we only currently support the 3.x protocols, this can be assumed here.
                // If and when 4.x comes out, this will need to be fixed--however, the entire 
                // protocol will need to be updated then anyway :)
                verMajor = 3;

                // Figure out which version of the protocol this is:
                switch (b[10])
                {
                    case 0x33:
                    case 0x36:	// BUG FIX: pass 3.3 for 3.6 to allow UltraVNC to work, thanks to Steve Bostedor.
                        verMinor = 3;
                        break;
                    case 0x37:
                        verMinor = 7;
                        break;
                    case 0x38:
                        verMinor = 8;
                        break;
                    case 0x39:  // BUG FIX: Apple reports 3.889
                        // According to the RealVNC mailing list, Apple is really using 3.3 
                        // (see http://www.mail-archive.com/vnc-list@realvnc.com/msg23615.html).  I've tested with
                        // both 3.3 and 3.8, and they both seem to work (I obviously haven't hit the issues others have).
                        // Because 3.8 seems to work, I'm leaving that, but it might be necessary to use 3.3 in future.
                        verMinor = 8;
                        break;
                }
            }
            else
            {
                throw new NotSupportedException("Only versions 3.3, 3.7, and 3.8 of the RFB Protocol are supported.");
            }
        }

        public void WriteSecurityTypes()
        {
            byte b = 1;
            serverWriter.Write(b);
            serverWriter.Flush();

            b = 2;
            serverWriter.Write(b);
            serverWriter.Flush();
        }

        protected sealed class BigEndianBinaryReader : BinaryReader
        {
            private byte[] buff = new byte[4];

            public BigEndianBinaryReader(System.IO.Stream input)
                : base(input)
            {
            }

            public BigEndianBinaryReader(System.IO.Stream input, System.Text.Encoding encoding)
                : base(input, encoding)
            {
            }

            // Since this is being used to communicate with an RFB host, only some of the overrides are provided below.

            public override ushort ReadUInt16()
            {
                FillBuff(2);
                return (ushort)(((uint)buff[1]) | ((uint)buff[0]) << 8);

            }

            public override short ReadInt16()
            {
                FillBuff(2);
                return (short)(buff[1] & 0xFF | buff[0] << 8);
            }

            public override uint ReadUInt32()
            {
                FillBuff(4);
                return (uint)(((uint)buff[3]) & 0xFF | ((uint)buff[2]) << 8 | ((uint)buff[1]) << 16 | ((uint)buff[0]) << 24);
            }

            public override int ReadInt32()
            {
                FillBuff(4);
                return (int)(buff[3] | buff[2] << 8 | buff[1] << 16 | buff[0] << 24);
            }

            private void FillBuff(int totalBytes)
            {
                int bytesRead = 0;
                int n = 0;

                do
                {
                    n = BaseStream.Read(buff, bytesRead, totalBytes - bytesRead);

                    if (n == 0)
                        throw new IOException("Unable to read next byte(s).");

                    bytesRead += n;
                } while (bytesRead < totalBytes);
            }
        }

        protected sealed class BigEndianBinaryWriter : BinaryWriter
        {
            public BigEndianBinaryWriter(System.IO.Stream input)
                : base(input)
            {
            }

            public BigEndianBinaryWriter(System.IO.Stream input, System.Text.Encoding encoding)
                : base(input, encoding)
            {
            }

            // Flip all little-endian .NET types into big-endian order and send
            public override void Write(ushort value)
            {
                FlipAndWrite(BitConverter.GetBytes(value));
            }

            public override void Write(short value)
            {
                FlipAndWrite(BitConverter.GetBytes(value));
            }

            public override void Write(uint value)
            {
                FlipAndWrite(BitConverter.GetBytes(value));
            }

            public override void Write(int value)
            {
                FlipAndWrite(BitConverter.GetBytes(value));
            }

            public override void Write(ulong value)
            {
                FlipAndWrite(BitConverter.GetBytes(value));
            }

            public override void Write(long value)
            {
                FlipAndWrite(BitConverter.GetBytes(value));
            }

            private void FlipAndWrite(byte[] b)
            {
                // Given an array of bytes, flip and write to underlying stream
                Array.Reverse(b);
                base.Write(b);
            }
        }
    }
}
